package com.alibaba.nacos.client.naming.net;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
import com.alibaba.nacos.api.naming.pojo.Service;
import com.alibaba.nacos.api.selector.AbstractSelector;
import com.alibaba.nacos.api.selector.ExpressionSelector;
import com.alibaba.nacos.api.selector.SelectorType;
import com.alibaba.nacos.client.config.impl.SpasAdapter;
import com.alibaba.nacos.client.monitor.MetricsMonitor;
import com.alibaba.nacos.client.naming.beat.BeatInfo;
import com.alibaba.nacos.client.naming.net.HttpClient.HttpResult;
import com.alibaba.nacos.client.naming.utils.*;
import com.alibaba.nacos.client.utils.AppNameUtils;
import com.alibaba.nacos.client.utils.LogUtils;
import com.alibaba.nacos.client.utils.TemplateUtils;
import com.alibaba.nacos.common.util.UuidUtils;
import com.alibaba.nacos.common.util.VersionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.concurrent.*;

public class NamingProxy {
    private static final int DEFAULT_SERVER_PORT = 8848;
    private int serverPort = 8848;
    private String namespaceId;
    private String endpoint;
    private String nacosDomain;
    private List<String> serverList;
    private List<String> serversFromEndpoint = new ArrayList();
    private long lastSrvRefTime = 0L;
    private long vipSrvRefInterMillis;
    private Properties properties;

    public NamingProxy(String namespaceId, String endpoint, String serverList) {
        this.vipSrvRefInterMillis = TimeUnit.SECONDS.toMillis(30L);
        this.namespaceId = namespaceId;
        this.endpoint = endpoint;
        if (StringUtils.isNotEmpty(serverList)) {
            this.serverList = Arrays.asList(serverList.split(","));
            if (this.serverList.size() == 1) {
                this.nacosDomain = serverList;
            }
        }

        this.initRefreshSrvIfNeed();
    }

    private void initRefreshSrvIfNeed() {
        if (!StringUtils.isEmpty(this.endpoint)) {
            ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    t.setName("com.alibaba.nacos.client.naming.serverlist.updater");
                    t.setDaemon(true);
                    return t;
                }
            });
            executorService.scheduleWithFixedDelay(new Runnable() {
                public void run() {
                    NamingProxy.this.refreshSrvIfNeed();
                }
            }, 0L, this.vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
            this.refreshSrvIfNeed();
        }
    }

    public List<String> getServerListFromEndpoint() {
        try {
            String urlString = "http://" + this.endpoint + "/nacos/serverlist";
            List<String> headers = this.builderHeaders();
            HttpResult result = HttpClient.httpGet(urlString, headers, (Map)null, "UTF-8");
            if (200 != result.code) {
                throw new IOException("Error while requesting: " + urlString + "'. Server returned: " + result.code);
            } else {
                String content = result.content;
                List<String> list = new ArrayList();
                Iterator var6 = IoUtils.readLines(new StringReader(content)).iterator();

                while(var6.hasNext()) {
                    String line = (String)var6.next();
                    if (!line.trim().isEmpty()) {
                        list.add(line.trim());
                    }
                }

                return list;
            }
        } catch (Exception var8) {
            var8.printStackTrace();
            return null;
        }
    }

    private void refreshSrvIfNeed() {
        try {
            if (!CollectionUtils.isEmpty(this.serverList)) {
                LogUtils.NAMING_LOGGER.debug("server list provided by user: " + this.serverList);
                return;
            }

            if (System.currentTimeMillis() - this.lastSrvRefTime < this.vipSrvRefInterMillis) {
                return;
            }

            List<String> list = this.getServerListFromEndpoint();
            if (CollectionUtils.isEmpty(list)) {
                throw new Exception("Can not acquire Nacos list");
            }

            if (!CollectionUtils.isEqualCollection(list, this.serversFromEndpoint)) {
                LogUtils.NAMING_LOGGER.info("[SERVER-LIST] server list is updated: " + list);
            }

            this.serversFromEndpoint = list;
            this.lastSrvRefTime = System.currentTimeMillis();
        } catch (Throwable var2) {
            LogUtils.NAMING_LOGGER.warn("failed to update server list", var2);
        }

    }

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
        Map<String, String> params = new HashMap(9);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        params.put("clusterName", instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put("healthy", String.valueOf(instance.isHealthy()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JSON.toJSONString(instance.getMetadata()));
        this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"POST");
    }

    public void deregisterService(String serviceName, Instance instance) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[DEREGISTER-SERVICE] {} deregistering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
        Map<String, String> params = new HashMap(8);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("clusterName", instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"DELETE");
    }

    public void updateInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[UPDATE-SERVICE] {} update service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
        Map<String, String> params = new HashMap(8);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        params.put("clusterName", instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enabled", String.valueOf(instance.isEnabled()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JSON.toJSONString(instance.getMetadata()));
        this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"PUT");
    }

    public Service queryService(String serviceName, String groupName) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[QUERY-SERVICE] {} query service : {}, {}", new Object[]{this.namespaceId, serviceName, groupName});
        Map<String, String> params = new HashMap(3);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        String result = this.reqAPI(UtilAndComs.NACOS_URL_SERVICE, params, (String)"GET");
        JSONObject jsonObject = JSON.parseObject(result);
        return (Service)jsonObject.toJavaObject(Service.class);
    }

    public void createService(Service service, AbstractSelector selector) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[CREATE-SERVICE] {} creating service : {}", this.namespaceId, service);
        Map<String, String> params = new HashMap(6);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", service.getName());
        params.put("groupName", service.getGroupName());
        params.put("protectThreshold", String.valueOf(service.getProtectThreshold()));
        params.put("metadata", JSON.toJSONString(service.getMetadata()));
        params.put("selector", JSON.toJSONString(selector));
        this.reqAPI(UtilAndComs.NACOS_URL_SERVICE, params, (String)"POST");
    }

    public boolean deleteService(String serviceName, String groupName) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[DELETE-SERVICE] {} deleting service : {} with groupName : {}", new Object[]{this.namespaceId, serviceName, groupName});
        Map<String, String> params = new HashMap(6);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        String result = this.reqAPI(UtilAndComs.NACOS_URL_SERVICE, params, (String)"DELETE");
        return "ok".equals(result);
    }

    public void updateService(Service service, AbstractSelector selector) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[UPDATE-SERVICE] {} updating service : {}", this.namespaceId, service);
        Map<String, String> params = new HashMap(6);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", service.getName());
        params.put("groupName", service.getGroupName());
        params.put("protectThreshold", String.valueOf(service.getProtectThreshold()));
        params.put("metadata", JSON.toJSONString(service.getMetadata()));
        params.put("selector", JSON.toJSONString(selector));
        this.reqAPI(UtilAndComs.NACOS_URL_SERVICE, params, (String)"PUT");
    }

    public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly) throws NacosException {
        Map<String, String> params = new HashMap(8);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("clusters", clusters);
        params.put("udpPort", String.valueOf(udpPort));
        params.put("clientIP", NetUtils.localIP());
        params.put("healthyOnly", String.valueOf(healthyOnly));
        return this.reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/list", params, (String)"GET");
    }

    public long sendBeat(BeatInfo beatInfo) {
        try {
            if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
                LogUtils.NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", this.namespaceId, beatInfo.toString());
            }

            Map<String, String> params = new HashMap(4);
            params.put("beat", JSON.toJSONString(beatInfo));
            params.put("namespaceId", this.namespaceId);
            params.put("serviceName", beatInfo.getServiceName());
            String result = this.reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, (String)"PUT");
            JSONObject jsonObject = JSON.parseObject(result);
            if (jsonObject != null) {
                return jsonObject.getLong("clientBeatInterval");
            }
        } catch (Exception var5) {
            LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: " + JSON.toJSONString(beatInfo), var5);
        }

        return 0L;
    }

    public String setWeight(BeatInfo beatInfo) {
        try {
            if (LogUtils.NAMING_LOGGER.isDebugEnabled()) {
                LogUtils.NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", this.namespaceId, beatInfo.toString());
            }

            Map<String, String> params = new HashMap(4);

            params.put("namespaceId", this.namespaceId);
            params.put("serviceName", beatInfo.getServiceName());
            params.put("ip", beatInfo.getIp());
            params.put("port", beatInfo.getPort()+"");
            params.put("weight", beatInfo.getWeight()+"");
            return this.reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance", params, (String)"PUT");
        } catch (Exception var5) {
            LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to set weight: " + JSON.toJSONString(beatInfo), var5);
        }

        return null;
    }

    public boolean serverHealthy() {
        try {
            String result = this.reqAPI(UtilAndComs.NACOS_URL_BASE + "/operator/metrics", new HashMap(2));
            JSONObject json = JSON.parseObject(result);
            String serverStatus = json.getString("status");
            return "UP".equals(serverStatus);
        } catch (Exception var4) {
            return false;
        }
    }

    public ListView<String> getServiceList(int pageNo, int pageSize, String groupName) throws NacosException {
        return this.getServiceList(pageNo, pageSize, groupName, (AbstractSelector)null);
    }

    public ListView<String> getServiceList(int pageNo, int pageSize, String groupName, AbstractSelector selector) throws NacosException {
        Map<String, String> params = new HashMap(4);
        params.put("pageNo", String.valueOf(pageNo));
        params.put("pageSize", String.valueOf(pageSize));
        params.put("namespaceId", this.namespaceId);
        params.put("groupName", groupName);
        if (selector != null) {
            switch(SelectorType.valueOf(selector.getType())) {
                case none:
                default:
                    break;
                case label:
                    ExpressionSelector expressionSelector = (ExpressionSelector)selector;
                    params.put("selector", JSON.toJSONString(expressionSelector));
            }
        }

        String result = this.reqAPI(UtilAndComs.NACOS_URL_BASE + "/service/list", params);
        JSONObject json = JSON.parseObject(result);
        ListView<String> listView = new ListView();
        listView.setCount(json.getInteger("count"));
        listView.setData((List) JSON.parseObject(json.getString("doms"), new TypeReference<List<String>>() {
        }, new Feature[0]));
        return listView;
    }

    public String reqAPI(String api, Map<String, String> params) throws NacosException {
        List<String> snapshot = this.serversFromEndpoint;
        if (!CollectionUtils.isEmpty(this.serverList)) {
            snapshot = this.serverList;
        }

        return this.reqAPI(api, params, snapshot);
    }

    public String reqAPI(String api, Map<String, String> params, String method) throws NacosException {
        List<String> snapshot = this.serversFromEndpoint;
        if (!CollectionUtils.isEmpty(this.serverList)) {
            snapshot = this.serverList;
        }

        return this.reqAPI(api, params, snapshot, method);
    }

    public String callServer(String api, Map<String, String> params, String curServer) throws NacosException {
        return this.callServer(api, params, curServer, "GET");
    }

    public String callServer(String api, Map<String, String> params, String curServer, String method) throws NacosException {
        long start = System.currentTimeMillis();
        long end = 0L;
        this.checkSignature(params);
        List<String> headers = this.builderHeaders();
        String url;
        if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {
            if (!curServer.contains(":")) {
                curServer = curServer + ":" + this.serverPort;
            }

            url = HttpClient.getPrefix() + curServer + api;
        } else {
            url = curServer + api;
        }

        HttpResult result = HttpClient.request(url, headers, params, "UTF-8", method);
        end = System.currentTimeMillis();
        MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(result.code)).observe((double)(end - start));
        if (200 == result.code) {
            return result.content;
        } else if (304 == result.code) {
            return "";
        } else {
            throw new NacosException(500, "failed to req API:" + curServer + api + ". code:" + result.code + " msg: " + result.content);
        }
    }

    public String reqAPI(String api, Map<String, String> params, List<String> servers) {
        return this.reqAPI(api, params, servers, "GET");
    }

    public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) {
        params.put("namespaceId", this.getNamespaceId());
        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(this.nacosDomain)) {
            throw new IllegalArgumentException("no server available");
        } else {
            Exception exception = new Exception();
            if (servers != null && !servers.isEmpty()) {
                Random random = new Random(System.currentTimeMillis());
                int index = random.nextInt(servers.size());

                for(int i = 0; i < servers.size(); ++i) {
                    String server = (String)servers.get(index);

                    try {
                        return this.callServer(api, params, server, method);
                    } catch (NacosException var11) {
                        exception = var11;
                        LogUtils.NAMING_LOGGER.error("request {} failed.", server, var11);
                    } catch (Exception var12) {
                        exception = var12;
                        LogUtils.NAMING_LOGGER.error("request {} failed.", server, var12);
                    }

                    index = (index + 1) % servers.size();
                }

                throw new IllegalStateException("failed to req API:" + api + " after all servers(" + servers + ") tried: " + ((Exception)exception).getMessage());
            } else {
                int i = 0;

                while(i < 3) {
                    try {
                        return this.callServer(api, params, this.nacosDomain);
                    } catch (Exception var13) {
                        exception = var13;
                        LogUtils.NAMING_LOGGER.error("[NA] req api:" + api + " failed, server(" + this.nacosDomain, var13);
                        ++i;
                    }
                }

                throw new IllegalStateException("failed to req API:/api/" + api + " after all servers(" + servers + ") tried: " + ((Exception)exception).getMessage());
            }
        }
    }

    private void checkSignature(Map<String, String> params) {
        String ak = this.getAccessKey();
        String sk = this.getSecretKey();
        params.put("app", AppNameUtils.getAppName());
        if (!StringUtils.isEmpty(ak) || !StringUtils.isEmpty(sk)) {
            try {
                String signData = getSignData((String)params.get("serviceName"));
                String signature = SignUtil.sign(signData, sk);
                params.put("signature", signature);
                params.put("data", signData);
                params.put("ak", ak);
            } catch (Exception var6) {
                var6.printStackTrace();
            }

        }
    }

    public List<String> builderHeaders() {
        List<String> headers = Arrays.asList("Client-Version", VersionUtils.VERSION, "User-Agent", UtilAndComs.VERSION, "Accept-Encoding", "gzip,deflate,sdch", "Connection", "Keep-Alive", "RequestId", UuidUtils.generateUuid(), "Request-Module", "Naming");
        return headers;
    }

    private static String getSignData(String serviceName) {
        return StringUtils.isNotEmpty(serviceName) ? System.currentTimeMillis() + "@@" + serviceName : String.valueOf(System.currentTimeMillis());
    }

    public String getAccessKey() {
        return this.properties == null ? SpasAdapter.getAk() : TemplateUtils.stringEmptyAndThenExecute(this.properties.getProperty("accessKey"), new Callable<String>() {
            public String call() {
                return SpasAdapter.getAk();
            }
        });
    }

    public String getSecretKey() {
        return this.properties == null ? SpasAdapter.getSk() : TemplateUtils.stringEmptyAndThenExecute(this.properties.getProperty("secretKey"), new Callable<String>() {
            public String call() throws Exception {
                return SpasAdapter.getSk();
            }
        });
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
        this.setServerPort(8848);
    }

    public String getNamespaceId() {
        return this.namespaceId;
    }

    public void setServerPort(int serverPort) {
        this.serverPort = serverPort;
        String sp = System.getProperty("nacos.naming.exposed.port");
        if (StringUtils.isNotBlank(sp)) {
            this.serverPort = Integer.parseInt(sp);
        }

    }
}
